#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <png.h>
#include <MY_LOG.h>

using namespace std;
using namespace cv;

#define NUM_BYTES_SIG 8

void my_fn(png_structp png_ptr, const char * msg) {
    MY_LOG((char *)msg);
}

/**
 * argc:2 -> argv[1] = input_image
 * argc:5 -> argv[1,2,3,4,5] = {input_image, width, height, pixel_depth, channels}
 */
int main(int argc, char *argv[]) {
    MY_ASSERT(argc == 2);

    const char *cInputPath = argv[1];
    FILE *fInputImage = fopen(cInputPath, "r");
    MY_ASSERT(fInputImage != NULL);

    //verify if png
    unsigned char cHeader[NUM_BYTES_SIG];
    MY_ASSERT(NUM_BYTES_SIG == fread(cHeader, 1, NUM_BYTES_SIG, fInputImage));

    /*
    for (int i = 0; i < NUM_BYTES_SIG; i ++) {
        MY_LOG("%d: %d\n", i, cHeader[i]);
    }
    */
    
    MY_ASSERT(0 == png_sig_cmp(cHeader, 0, NUM_BYTES_SIG));

    // start to read
    png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, my_fn, my_fn);
    png_infop info_ptr = png_create_info_struct(png_ptr);
    png_init_io(png_ptr, fInputImage);
    png_set_sig_bytes(png_ptr, NUM_BYTES_SIG);
    png_bytepp row_pointers = NULL;

    //read info
    png_uint_32 PNG_WIDTH, PNG_HEIGHT;
    int PNG_BIT_DEPTH, PNG_COLOR_TYPE, PNG_INTERLACE_TYPE, PNG_COMPRESSION_TYPE, PNG_FILTER_TYPE, PNG_CHANNELS;
    int PNG_NUM_PASSES = 0;
    png_read_info(png_ptr, info_ptr);
    png_get_IHDR(png_ptr, info_ptr, &PNG_WIDTH, &PNG_HEIGHT, &PNG_BIT_DEPTH, &PNG_COLOR_TYPE, &PNG_INTERLACE_TYPE, &PNG_COMPRESSION_TYPE, &PNG_FILTER_TYPE);
    PNG_CHANNELS = png_get_channels(png_ptr, info_ptr);
    MY_LOG("before %s : %lu x %lu, bit_depth:%d, color_type:%d, channels:%d, interlace_type:%d, compression_type:%d, filter_type:%d\n", 
            cInputPath, PNG_WIDTH, PNG_HEIGHT, PNG_BIT_DEPTH, PNG_COLOR_TYPE, PNG_CHANNELS, PNG_INTERLACE_TYPE, PNG_COMPRESSION_TYPE, PNG_FILTER_TYPE);

    switch (PNG_COLOR_TYPE) {
        case PNG_COLOR_TYPE_PALETTE:
            png_set_palette_to_rgb(png_ptr);
            if(PNG_BIT_DEPTH < 8) {
                png_set_packing(png_ptr);
            }
            break;
        case PNG_COLOR_TYPE_GRAY:
            if (PNG_BIT_DEPTH < 8) {
                png_set_expand_gray_1_2_4_to_8(png_ptr);
            }
        case PNG_COLOR_TYPE_GRAY_ALPHA:
            //how to deal with grayscale with alpha
            png_set_gray_to_rgb(png_ptr);
            break;
    }
    //don't support 16 bit, so if the image Has 16 bits per channel, round it
    if (PNG_BIT_DEPTH == 16) {
        png_set_strip_16(png_ptr);
    }
    //if the image has a transperancy set.. convert it to a full Alpha channel
    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
        png_set_tRNS_to_alpha(png_ptr);
    }
    PNG_NUM_PASSES = png_set_interlace_handling(png_ptr);

    //verify png_set_bgr works: yes
    png_set_bgr(png_ptr);
    
    png_read_update_info(png_ptr, info_ptr);
    png_get_IHDR(png_ptr, info_ptr, &PNG_WIDTH, &PNG_HEIGHT, &PNG_BIT_DEPTH, &PNG_COLOR_TYPE, &PNG_INTERLACE_TYPE, &PNG_COMPRESSION_TYPE, &PNG_FILTER_TYPE);
    PNG_CHANNELS = png_get_channels(png_ptr, info_ptr);
    MY_LOG("after %s : %lu x %lu, bit_depth:%d, color_type:%d, channels:%d, interlace_type:%d, compression_type:%d, filter_type:%d\n", 
            cInputPath, PNG_WIDTH, PNG_HEIGHT, PNG_BIT_DEPTH, PNG_COLOR_TYPE, PNG_CHANNELS, PNG_INTERLACE_TYPE, PNG_COMPRESSION_TYPE, PNG_FILTER_TYPE);

    int PNG_ROWBYTES = png_get_rowbytes(png_ptr, info_ptr);
    MY_ASSERT(PNG_WIDTH * PNG_BIT_DEPTH/8 * PNG_CHANNELS == PNG_ROWBYTES);

    //opencv start
    MY_ASSERT(PNG_BIT_DEPTH == IPL_DEPTH_8U);
    MY_ASSERT(PNG_CHANNELS == 3 || PNG_CHANNELS == 4);

    IplImage *iplRGBA = cvCreateImageHeader(cvSize(PNG_WIDTH, PNG_HEIGHT), IPL_DEPTH_8U, PNG_CHANNELS);
    MY_ASSERT(iplRGBA -> widthStep >= PNG_ROWBYTES); //cause opencv using 4-bytes align
    cvNamedWindow(cInputPath, CV_WINDOW_AUTOSIZE);
    cvMoveWindow(cInputPath, 0, 0);
    char *rgbCV = (char *) malloc (PNG_HEIGHT * iplRGBA -> widthStep);
    cvSetData(iplRGBA, (char *)rgbCV, iplRGBA->widthStep);
    //verfiy png_set_bgr works: yes
    IplImage *iplBGRA = iplRGBA;

    row_pointers = (png_bytep *) png_malloc(png_ptr, PNG_HEIGHT*(sizeof (png_bytep)));
    for (int i = 0; i < PNG_HEIGHT; i++) {
        //discard this, using the iplimage's data
        //row_pointers[i]=(png_bytep) png_malloc(png_ptr, PNG_ROWBYTES);
        row_pointers[i] = (png_bytep) (rgbCV + i * iplRGBA->widthStep);
    }
    
    //simpler one(read whole image at once):
    //png_read_image(png_ptr, row_pointers);

    // complex one
    for(int ipass = 1; ipass <= PNG_NUM_PASSES; ipass ++) {
        //MY_LOG("pass: %d/%d\n", ipass, PNG_NUM_PASSES);
        //rectangle
        png_read_rows(png_ptr, NULL, row_pointers, PNG_HEIGHT);
        //sparkle
        //png_read_rows(png_ptr, row_pointers, NULL, PNG_HEIGHT);
        for(int i = 0;  i< PNG_HEIGHT; i ++) {
            //discard this, using the iplimage's data directly
            //memcpy(rgbCV + i * iplRGBA->widthStep, row_pointers[i], PNG_ROWBYTES);
        }

        cvShowImage(cInputPath, iplBGRA);
        cvWaitKey(100);
    }

    cvShowImage(cInputPath, iplBGRA);
    cvWaitKey(0);
    fclose(fInputImage);
 
    return 0;
}
